using System;
using System.Collections.Generic;
using Jink.Core.Converters;
using Jink.Core.Converters.Context;
using Jink.Core.Handlers.Annotations;
using Jink.Core.Urls;
using Jink.Protocol.Core.Verbs;
using XenoGears.Collections.Dictionaries;
using XenoGears.Functional;
using System.Linq;
using XenoGears.Reflection;
using XenoGears.Reflection.Attributes;
using XenoGears.Reflection.Shortcuts;
using XenoGears.Reflection.Simple;

namespace Jink.Core.Handlers.Bind
{
    public class BindResult
    {
        public CoreRequest Request { get; private set; }
        public String ActualResource { get { return Request.Resource; } }
        public Verb ActualVerb { get { return Request.Verb; } }
        public ReadOnlyDictionary<String, String> ActualArgs { get; private set; }

        public Handler Handler { get; private set; }
        public String ExpectedResource { get { return Handler.UriTemplate; } }
        public Verb ExpectedVerb { get { return Handler.Verb; } }
        public ReadOnlyDictionary<String, Type> ExpectedArgs { get { return Handler.Args; } }

        public BindStatus Status { get; private set; }
        public bool Success { get { return Status == BindStatus.Success; } }
        public BoundHandler BoundHandler { get; private set; }

        public BindResult(CoreRequest request, Handler handler)
        {
            Request = request;
            Handler = handler;

            var uriFragments = new Url(request.Resource).Parse(handler.UriTemplate);
            if (uriFragments == null)
            {
                Status = BindStatus.UrlMismatch;
            }
            else
            {
                if (request.Verb != handler.Verb)
                {
                    Status = BindStatus.VerbMismatch;
                }
                else
                {
                    var actualArgs = new Dictionary<String, String>();
                    actualArgs.AddElements(Request.Arguments);
                    actualArgs.AddElements(uriFragments);
                    ActualArgs = actualArgs.ToReadOnly();

                    var entries = actualArgs.Select(kvp => new ConversionContextEntry(
                        typeof(String), kvp.Value, new {name = kvp.Key}));
                    var bind = Converter.Convert(entries, ExpectedArgs, new {jink = ""});

                    if (bind == null)
                    {
                        Status = BindStatus.ArgumentsMismatch;
                    }
                    else
                    {
                        var m = Handler.Code;

                        Object target = null;
                        if (!m.IsStatic)
                        {
                            target = m.DeclaringType.CreateInstance();

                            var fields = m.DeclaringType.GetFields(BF.AllInstance).Where(f => f.HasAttr<BindAttribute>());
                            var props = m.DeclaringType.GetProperties(BF.AllInstance).Where(f => f.HasAttr<BindAttribute>());
                            var slots = fields.Select(f => f.Name).Concat(props.Select(p => p.Name));
                            slots.ForEach(name => target.Set(name, bind[name]));
                        }

                        Status = BindStatus.Success;
                        var args = m.GetParameters().Select(pi => pi.Name);
                        BoundHandler = () => m.Invoke(target, args.Select(name => bind[name]).ToArray());
                    }
                }
            }
        }
    }
}